/******************************************************************************* * Copyright 2014 Miami-Dade County * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.sharegov.cirm.rest; import static mjson.Json.array; import static mjson.Json.object; import static mjson.Json.read; import static org.sharegov.cirm.OWL.dataProperty; import static org.sharegov.cirm.OWL.individual; import static org.sharegov.cirm.OWL.objectProperties; import static org.sharegov.cirm.OWL.objectProperty; import static org.sharegov.cirm.OWL.owlClass; import static org.sharegov.cirm.OWL.reasoner; import static org.sharegov.cirm.Refs.topOntology; import static org.sharegov.cirm.rdb.Sql.SELECT; import static org.sharegov.cirm.rest.OperationService.getPersister; import static org.sharegov.cirm.utils.GenUtils.ko; import static org.sharegov.cirm.utils.GenUtils.ok; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.StringReader; import java.io.StringWriter; import java.math.BigDecimal; import java.sql.SQLException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import mjson.Json; import org.hypergraphdb.util.RefResolver; import org.restlet.Response; import org.restlet.data.MediaType; import org.restlet.representation.OutputRepresentation; import org.restlet.representation.Representation; import org.semanticweb.owlapi.model.AddAxiom; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLEntity; import org.semanticweb.owlapi.model.OWLIndividualAxiom; import org.semanticweb.owlapi.model.OWLLiteral; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLObject; import org.semanticweb.owlapi.model.OWLObjectPropertyAssertionAxiom; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyChange; import org.semanticweb.owlapi.model.OWLOntologyCreationException; import org.semanticweb.owlapi.model.OWLOntologyManager; import org.semanticweb.owlapi.model.SWRLBuiltInAtom; import org.semanticweb.owlapi.model.SWRLDArgument; import org.semanticweb.owlapi.model.SWRLVariable; import org.semanticweb.owlapi.vocab.OWL2Datatype; import org.sharegov.cirm.BOntology; import org.sharegov.cirm.CirmTransaction; import org.sharegov.cirm.CirmTransactionEvent; import org.sharegov.cirm.CirmTransactionListener; import org.sharegov.cirm.OWL; import org.sharegov.cirm.Refs; import org.sharegov.cirm.StartUp; import org.sharegov.cirm.event.EventDispatcher; import org.sharegov.cirm.gis.GisDAO; import org.sharegov.cirm.legacy.ActivityManager; import org.sharegov.cirm.legacy.CirmMessage; import org.sharegov.cirm.legacy.MessageManager; import org.sharegov.cirm.legacy.Permissions; import org.sharegov.cirm.owl.Model; import org.sharegov.cirm.process.AddTxnListenerForNewSR; import org.sharegov.cirm.process.ApprovalProcess; import org.sharegov.cirm.process.AttachSendEmailListener; import org.sharegov.cirm.process.CreateDefaultActivities; import org.sharegov.cirm.process.CreateNewSREmail; import org.sharegov.cirm.process.PopulateGisData; import org.sharegov.cirm.process.SaveOntology; import org.sharegov.cirm.rdb.Concepts; import org.sharegov.cirm.rdb.DBIDFactory; import org.sharegov.cirm.rdb.Query; import org.sharegov.cirm.rdb.QueryTranslator; import org.sharegov.cirm.rdb.RelationalOWLPersister; import org.sharegov.cirm.rdb.RelationalStore; import org.sharegov.cirm.rdb.Sql; import org.sharegov.cirm.rdb.Statement; import org.sharegov.cirm.stats.CirmStatistics; import org.sharegov.cirm.stats.CirmStatisticsFactory; import org.sharegov.cirm.stats.SRCirmStatsDataReporter; import org.sharegov.cirm.utils.ConcurrentLockedToOpenException; import org.sharegov.cirm.utils.ExcelExportUtil; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.JsonUtil; import org.sharegov.cirm.utils.PDFExportUtil; import org.sharegov.cirm.utils.PDFViewReport; import org.sharegov.cirm.utils.RemoveAttachmentsOnTxSuccessListener; import org.sharegov.cirm.utils.SendEmailOnTxSuccessListener; import org.sharegov.cirm.utils.ThreadLocalStopwatch; import org.sharegov.cirm.workflows.WebServiceCallTask; @Path("legacy") @Produces("application/json") public class LegacyEmulator extends RestService { public static boolean DBG = true; public static boolean DBGSQL = false; public static final int MAX_CALLWS_ATTEMPTS = 3; private static Logger logger = Logger.getLogger("org.sharegov.cirm"); private static Map<String, IRI> hasTypeMappingToXSD; private final SRCirmStatsDataReporter srStatsReporter = CirmStatisticsFactory.createServiceRequestStatsReporter(Refs.stats.resolve(), "LegacyEmulator"); public LegacyEmulator() { } /** * Gets a type mapping from question type to xsd type. * Tread Safe double checked. * @author hilpold * @return */ public Map<String, IRI> getHasTypeMappingToXSD() { if (hasTypeMappingToXSD == null) { synchronized (LegacyEmulator.class) { if (hasTypeMappingToXSD == null) initializeHasTypeMappingToXSD(); } } return hasTypeMappingToXSD; } private static void initializeHasTypeMappingToXSD() { HashMap<String, IRI> newTypeMappingToXSD = new HashMap<String, IRI>(); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_CHAR, OWL2Datatype.XSD_STRING.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_CHARLIST, OWL2Datatype.XSD_STRING.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_CHARMULT, OWL2Datatype.XSD_STRING.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_CHAROPT, OWL2Datatype.XSD_STRING.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_DATE, OWL2Datatype.XSD_DATE_TIME_STAMP.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_NUMBER, OWL2Datatype.XSD_INTEGER.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_DOUBLE, OWL2Datatype.XSD_DOUBLE.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_PHONENUM, OWL2Datatype.XSD_STRING.getIRI()); newTypeMappingToXSD.put(LegacyEmulatorConstants.QUESTION_TYPE_TIME, OWL2Datatype.XSD_STRING.getIRI()); // hilpold Thread safety: Set static var as last operation to prevent publishing it before map is fully initialized hasTypeMappingToXSD = newTypeMappingToXSD; } //static String lastqas; //static Json srtypelist; private static void sortTheFields(List<Json> allFields) { Collections.sort(allFields, new Comparator<Json>() { public int compare(Json left, Json right) { Float f = (left.at("hasOrderBy").asFloat() - right.at( "hasOrderBy").asFloat()); if (f > 0) return 1; else if (f < 0) return -1; else return 0; } }); } private Json ontoToJson(BOntology srontology) { Json result = srontology.toJSON(); GenUtils.ensureArray(result.at("properties"), "hasServiceActivity"); GenUtils.ensureArray(result.at("properties"), "hasServiceAnswer"); GenUtils.ensureArray(result.at("properties"), "hasServiceCaseActor"); return result; } private Json getAgencies(OWLClass cl) { Json result = object(); // OWLOntology O = MetaService.get().getMetaOntology(); Set<OWLNamedIndividual> S = reasoner().getInstances(cl, true) .getFlattened(); for (OWLNamedIndividual ind : S) { Json x = result.at(ind.getIRI().toString(), object().set("label", OWL.getEntityLabel(ind))); // City_Organization and (hasParentAgency value City_of_Miami) Set<OWLNamedIndividual> subs = reasoner() .getInstances( OWL.and(cl, OWL.has( objectProperty("hasParentAgency"), ind)), false).getFlattened(); // reasoner(O).getInstances(cl, false).getFlattened(); Json A = x.at("agencies", array()); for (OWLNamedIndividual sind : subs) { A.add(object("iri", sind.getIRI().toString(), "label", OWL.getEntityLabel(sind))); } } return result; } @GET @Path("/getHasTypeMappingToXSD") @Produces("application/json") public Json getHasTypeMappingToXSDMap() { Json result = Json.object(); for (Map.Entry<String, IRI> typeToXSD : getHasTypeMappingToXSD() .entrySet()) { result.set(typeToXSD.getKey(), typeToXSD.getValue().toString()); } return result; } @GET @Path("/searchAgencyMap") @Produces("application/json") public Json getSearchAgencyMap() { // we want at top-level: the county, all cities, state gov,fed gov and // private enterprise (non-gov) // i.e. all direct instances of county-gov, city_organization return object().with(getAgencies(owlClass("City_Organization"))) .with(getAgencies(owlClass("County_Organization"))) .with(getAgencies(owlClass("State_Organization"))) .with(getAgencies(owlClass("Federal_Organization"))); } // TODO: this could potentially go, it's used in the PDFViewReport, but // seems like the use // there is opportunistic and not necessary. public static void getAllServiceFields(OWLNamedIndividual type, List<Json> allServiceAnswers, boolean sort) { for (OWLNamedIndividual field : objectProperties(type, "legacy:hasServiceField")) { String iri = field.getIRI().toString(); Json hasServiceAnswer = object("hasAnswerValue", object("literal", "", "type", ""), "hasServiceField", object("iri", iri)); String label = OWL.getEntityLabel(field); if (label != null) hasServiceAnswer.at("hasServiceField").set("label", label); OWLLiteral dprop = dataProperty(field, "legacy:hasOrderBy"); if (dprop != null) hasServiceAnswer.set("hasOrderBy", Float.parseFloat(dprop.getLiteral())); dprop = dataProperty(field, "legacy:hasDataType"); if (dprop != null) hasServiceAnswer.set("hasDataType", dprop.getLiteral()); dprop = dataProperty(field, "legacy:hasBusinessCodes"); if (dprop != null) hasServiceAnswer.set("hasBusinessCodes", dprop.getLiteral()); allServiceAnswers.add(hasServiceAnswer); } if (sort) sortTheFields(allServiceAnswers); } @GET @Path("hitme") public Json hitme() { return ok().set("msg", "BOOM!"); } /** * Returns the 'boid' (business object ID) of a service case. Use this method * when you have a string that identifies a service case somehow and you need * the numeric DB identifier. * * This method will try to do the smart thing depending on its input. If the input is * already a number, that'll be the result. If the looks like a case number (e.g. "13-10004345"), * then a database lookup is performed to find its boid. If it looks like an IRI, then the boid * is extracted from the IRI following the naming convention. * * @param s A string representation of a long, a case number or an IRI. * @return */ public long toServiceCaseId(String s) { try { return Long.parseLong(s); } catch (Throwable t) { } try { IRI iri = IRI.create(s); OWL.parseIDFromBusinessOntologyIRI(iri); } catch (Throwable t) { } return lookupServiceCaseId(Json.object("legacy:hasCaseNumber", s, "type", "legacy:ServiceCase")); } /** * <p> * Find a case by its "user friendly" case number. Return <code>Json.nil()</code> if case is not found. * </p> * @param caseNumber Formatted as YY-xxxxxxxx */ public Json lookupByCaseNumber(String caseNumber) { QueryTranslator qt = new QueryTranslator(); RelationalOWLPersister persister = getPersister(); RelationalStore store = persister.getStore(); Query q = qt.translate(Json.object("legacy:hasCaseNumber", caseNumber, "type", "legacy:ServiceCase"), store); Set<Long> results = store.query(q, Refs.tempOntoManager.resolve().getOWLDataFactory()); if (results.size() == 0) return Json.nil(); else return lookupServiceCase(results.iterator().next()); } public BOntology lookupServiceCase(String caseID) { try { RelationalOWLPersister persister = getPersister(); OWLEntity entity = persister.getStore().selectEntityByID( Long.valueOf(caseID), Refs.tempOntoManager.resolve().getOWLDataFactory()); if (entity != null) { BOntology bo = new BOntology( persister.getBusinessObjectOntology(entity.getIRI())); return bo; } else return null; } catch (Throwable e) { e.printStackTrace(); return null; } } // TODO: the gisAddressData should not be part of the properties, it should be directly // under the object, at the same level as the boid and type and other meta information. The // properties should really be only data that is part of the objects (the hasGisDataId is, but not // the full gisAddressData thingy). // -- Boris public void addAddressData(Json data) { if(data.at("properties").has("legacy:hasGisDataId") && !data.at("properties").has("gisAddressData")) { Json serviceLayersInfo = GisDAO.getGisData( data.at("properties").at("legacy:hasGisDataId").asString()); if(serviceLayersInfo.has("address")) data.at("properties"). set("gisAddressData", serviceLayersInfo.at("address")); } } public BOntology findServiceCaseOntology(long caseid) { // System.out.println(Arrays.asList(this.getUserGroups())); RelationalOWLPersister persister = OperationService.getPersister(); OWLEntity entity = persister.getStore().selectEntityByID(caseid, Refs.tempOntoManager.resolve().getOWLDataFactory()); return entity == null ? null : BOntology.isValidBO(entity.getIRI()) ? new BOntology(persister.getBusinessObjectOntology(entity.getIRI())) : null; } @GET @Path("/search") public Json lookupServiceCase(@QueryParam("id") long caseid) { try { BOntology bo = findServiceCaseOntology(caseid); if (bo == null) return ko("Case not found."); Json result = ontoToJson(bo); addAddressData(result); if (isClientExempt() || Permissions.check(individual("BO_View"), individual(bo.getTypeIRI("legacy")), getUserActors())) return ok().set("bo", result); else { ThreadLocalStopwatch.error("LE: lookupServiceCase Permission denied to " + caseid + " " + getUserInfo()); return ko("Permission denied."); } } catch (Throwable e) { e.printStackTrace(); return ko(e); } } public long lookupServiceCaseId(Json queryData) { QueryTranslator qt = new QueryTranslator(); RelationalOWLPersister persister = getPersister(); RelationalStore store = persister.getStore(); Query q = qt.translate(queryData, store); Set<Long> results = store.query(q, Refs.tempOntoManager.resolve().getOWLDataFactory()); if (results.size() == 0) return -1; else return results.iterator().next(); } /** * <p> * Retrieve a single service case by specifying some way to identify it in * the <code>query</code> parameter. * </p> * * @param query : Specifies some way to identify the case. If this is a number, * it is taken to be the <code>boid</code> of the case already. If it is a string, * we still look at how it's format and if we can parse it as a number, we assume it's * the boid as well. If it doesn't look like a number, it is taken to be the "user friendly" * case number stored in the <code>hasCaseNumber</code> * property. If the JSON is an object, it is taken to be a query that is executed through the RDBMs * <code>QueryTranslator</code>. Finally, if the JSON is an array, each element is interpreted * as a separate query and an array result is return for each of them. * * @return : If found, a Service Request in Json format and * if not found, a Case not found message. */ @POST @Path("/caseNumberSearch") @Produces("application/json") @Consumes("application/json") public Json lookupServiceCase(Json query) { if (query.isBoolean() || query.isNull()) return ko("Invalid query."); Json notfound = ko("Case not found."); if (query.isArray()) { Json A = Json.array(); for (Json x : query.asJsonList()) A.add(lookupServiceCase(x)); return A; } long caseid = -1; if (query.isNumber()) caseid = -1; if (query.isString()) { try { caseid = Long.parseLong(query.asString()); } catch (Throwable t) {} if (caseid == -1) query = Json.object("legacy:hasCaseNumber", query.asString(), "type", "legacy:ServiceCase"); } if (query.isObject()) { try { caseid = lookupServiceCaseId(query); } catch (Exception e) { e.printStackTrace(); return ko(e.getMessage()); } } return caseid == -1 ? notfound : lookupServiceCase(caseid); } private Json getExportData(final Json formData) { Json metaData = Json.object().set("boid", "SR ID") .set("type", "SR Type").set("fullAddress", "Address") .set("city", "City").set("zip", "Zip") .set("hasStatus", "Status") .set("lastActivityUpdatedDate", "Last Activity Date") .set("createdDate", "Created Date").set("columns", 8); if (formData.has("gisColumnName")) { metaData.set("gisColumn", formData.at("gisColumnName").asString()); metaData.set("columns", metaData.at("columns").asInteger() + 1); } Json data = lookupAdvancedSearch(formData).at("resultsArray"); if(formData.has("atAddress")) { if(formData.at("atAddress").has("sortBy")) formData.set("sortBy", formData.at("atAddress").at("sortBy").asString()); if(formData.at("atAddress").has("sortDirection")) formData.set("sortDirection", formData.at("atAddress").at("sortDirection").asString()); } if (formData.has("sortBy") && !formData.at("sortBy").asString().equals("")) { Collections.sort(data.asJsonList(), new Comparator<Json>() { public int compare(Json left, Json right) { String orderBy = formData.at("sortBy").asString(); if (orderBy.equals("boid") || orderBy.equals("zip")) { int a = left.at(orderBy).asString().equals("") ? 0 : left.at(orderBy).asInteger(); int b = right.at(orderBy).asString().equals("") ? 0 : right.at(orderBy).asInteger(); return a - b; } else if (orderBy.equals("createdDate") || orderBy.equals("lastActivityUpdatedDate")) { try { if (left.at(orderBy).asString().equals("") && right.at(orderBy).asString().equals("")) return 0; else if (left.at(orderBy).asString().equals("") || right.at(orderBy).asString().equals("")) return (left.at(orderBy).asString().equals("")) ? -1 : 1; else { DateFormat df = new SimpleDateFormat( "MM/dd/yyyy"); return df.parse(left.at(orderBy).asString()) .compareTo( df.parse(right.at(orderBy) .asString())); } } //Swallowing the exception, if date not parseable, returning 0. catch (ParseException e) { e.printStackTrace(); return 0; } } else { if(left.has(orderBy) && right.has(orderBy)) return left.at(orderBy).asString() .compareTo(right.at(orderBy).asString()); else if(orderBy.contains("legacy:") && left.has(orderBy.split(":")[1])) { return left.at(orderBy.split(":")[1]).asString() .compareTo(right.at(orderBy.split(":")[1]).asString()); } else return 0; } // return 0; } }); // Dsc order if (formData.has("sortDirection") && formData.at("sortDirection").asString().equals("desc")) Collections.reverse(data.asJsonList()); } Json allData = Json.object().set("data", data) .set("metaData", metaData); return allData; } @POST @Path("/exportToExcel/") @Produces("application/vnd.ms-excel") public Representation exportToExcel(@FormParam("formData") String formData) { final Json allData = getExportData(Json.read(formData)) .set("searchCriteria", Json.read(formData)); OutputRepresentation or = new OutputRepresentation( MediaType.APPLICATION_EXCEL) { @Override public void write(OutputStream out) throws IOException { try { ExcelExportUtil e = new ExcelExportUtil(); e.exportData(out, allData); } catch (Exception e) { e.printStackTrace(); } } }; return or; } @POST @Path("exportToPDF") @Produces("application/pdf") public Representation exportToPDF(@FormParam("formData") String formData) { Json form = Json.read(formData); final Json allData = getExportData(form); OutputRepresentation or = new OutputRepresentation( MediaType.APPLICATION_PDF) { @Override public void write(OutputStream out) throws IOException { try { PDFExportUtil pdf = new PDFExportUtil(); pdf.exportData(out, allData); } catch (Exception e) { e.printStackTrace(); } } }; return or; } /** * Prints the Service Request report in pdf format * @param boid : The boid of service request. * @return */ @POST @Path("printView") @Produces("application/pdf") public Representation printServiceRequest(@FormParam("boid") String boid) { Set<Long> boids = new HashSet<Long>(); boids.add(Long.parseLong(boid)); try { ThreadLocalStopwatch.start("START LE.printServiceRequest " + boid); Representation report = makePDFCaseReports(boids); ThreadLocalStopwatch.stop("END LE.printServiceRequest"); srStatsReporter.succeeded("printServiceRequest", CirmStatistics.UNKNOWN, boid); return report; } catch (Exception e) { ThreadLocalStopwatch.fail("FAIL LE.printServiceRequest " + boid); throw new RuntimeException(e); } } /** * Prints the list of Service Request reports in pdf format. * * @param formData : The Search Criteria entered in Basic Search Tab * @return */ @POST @Path("srView") @Produces("application/pdf") public Representation viewServiceRequests( @FormParam("formData") String formData) { Json pattern = Json.read(formData); if(!isClientExempt()) { GenUtils.ensureArray(pattern, "type"); List<Json> searchTypes = pattern.at("type").asJsonList(); boolean searchAllAllowed = "legacy:ServiceCase".equals(searchTypes.get(0).asString()); if(searchAllAllowed || searchTypes.size() > 1) { Set<OWLNamedIndividual> permittedTypes = Permissions. getAllowedObjectsOfClass( Permissions.BO_VIEW, owlClass("legacy:ServiceCase"), getUserActors()); List<String> permittedTypeList = new ArrayList<String>(); for(OWLNamedIndividual ind : permittedTypes) { String indIRIStr = ind.getIRI().toString(); if (searchAllAllowed || searchTypes.contains(Json.make(indIRIStr))) { permittedTypeList.add(indIRIStr); } } pattern.set("type", permittedTypeList); if(permittedTypeList.isEmpty()) throw new RuntimeException("Permission denied to all Service Request Types specified in search parameters"); } else if (searchTypes.size() == 1) { if (!Permissions.check(individual("BO_View"), individual(searchTypes.get(0).asString()), getUserActors())) throw new RuntimeException("Permission denied to Service Request Type specified in search parameters."); } else { throw new RuntimeException("No type indication was provided by Basic Search. Please contact tech team."); } } Set<Long> results = null; try { if (DBG) { //Trace for 100% CPU issue. System.out.println("formData passed into viewServiceRequests : "+formData); ThreadLocalStopwatch.getWatch().time("START viewServiceRequestsPDF"); } QueryTranslator qt = new QueryTranslator(); RelationalStore store = getPersister().getStore(); Query q = qt.translate(pattern, store); results = store.query(q, Refs.tempOntoManager.resolve().getOWLDataFactory()); //TODO: too many result? goodbye! return makePDFCaseReports(results); } catch (Exception e) { System.out.println("formData passed into viewServiceRequests : "+formData); throw new RuntimeException(e); } finally { if (DBG) ThreadLocalStopwatch.getWatch().time("END viewServiceRequestsPDF"); ThreadLocalStopwatch.dispose(); } } /** * Creates PDF reports * @param srIDs : Set of Service Request boids whose reports need to be built * @return */ public Representation makePDFCaseReports(Set<Long> srIDs) { final List<Long> boids = new ArrayList<Long>(srIDs); if(boids.size() > 1) Collections.sort(boids); OutputRepresentation or = new OutputRepresentation( MediaType.APPLICATION_PDF) { @Override public void write(OutputStream out) throws IOException { ByteArrayOutputStream baOut; if(boids.size() > 1) baOut = new ByteArrayOutputStream(1000000); else baOut = new ByteArrayOutputStream(10000); PDFViewReport pvr = new PDFViewReport(); try { pvr.generateReport(baOut, boids); } catch (Exception e) { e.printStackTrace(); baOut = new ByteArrayOutputStream(10000); pvr.errorReport(baOut); StringBuilder emailBody = new StringBuilder(""); String emailSubject = "Failed to generate Service Request PDF Report"; emailBody.append("An error occured while trying to generate the PDF report " + "for one of the following Service Requests:") .append("<br>").append(boids).append("<br>"); emailBody.append("The error details are :").append("<br><br>"); for(StackTraceElement element : e.getStackTrace()) { emailBody.append("at ").append(element.getClassName()) .append(".").append(element.getMethodName()) .append("(").append(element.getFileName()) .append(":").append(element.getLineNumber()) .append(")").append("<br>"); } MessageManager.get().sendEmail( "cirm@miamidade.gov", "CIAO-CIRMTT@miamidade.gov", emailSubject, emailBody.toString()); } baOut.writeTo(out); baOut.close(); } }; return or; } @POST @Path("hetRebateLetter") @Produces("application/pdf") public Representation getHETRebateLetter( @FormParam("applicantInfo") String applicant, @FormParam("hasCaseNumber") final String hasCaseNumber, @FormParam("isEnglish") String isEnglish) { final boolean isEng = Json.read(isEnglish).asBoolean(); final Json applicantActor = Json.read(applicant); OutputRepresentation or = new OutputRepresentation( MediaType.APPLICATION_PDF) { public void write(OutputStream out) throws IOException { ByteArrayOutputStream baOut = new ByteArrayOutputStream(10000); PDFViewReport pvr = new PDFViewReport(); try { pvr.generateHETRebateLetter(baOut, applicantActor, hasCaseNumber, isEng); } catch (Exception e) { e.printStackTrace(); baOut = new ByteArrayOutputStream(10000); //TODO : pvr.printErrorPage(baOut); } baOut.writeTo(out); baOut.close(); } }; return or; } // Remove the pagination and replace the select columns with count(*) for // the total count private long getSearchResultCount(Query q, RelationalStore store) throws SQLException { q.getStatement().getSql().CLEAR_PAGINATION(); q.getStatement().getSql().CLEAR_COLUMNS(); q.getStatement().getSql().COLUMN("count(*)").AS("RecordCount"); Set<Long> count = store.query(q, Refs.tempOntoManager.resolve().getOWLDataFactory()); if (count.size() == 1) return count.iterator().next(); else return 0; } /** * Fetches Service Requests from the db which satisfy the given search criteria * @param data - Search Criteria * @return Search Results */ // PROTECT @POST @Path("advSearch") @Produces("application/json") @Consumes("application/json") public Json lookupAdvancedSearch(Json data) { try { if (DBG) ThreadLocalStopwatch.getWatch().time("START lookupAdvancedSearch"); QueryTranslator qt = new QueryTranslator(); RelationalStore store = getPersister().getStore(); Json resultsArray = Json.array(); Query q = null; //If Gis Layer/Area is part of the Search criteria then the users //want to see the searched column as part of the result as well. Json gisSearch = object("isPresent", false); if (data.has("gisColumnName")) { gisSearch.set("isPresent", true).set("gisColumnName", data.at("gisColumnName").asString()); data.delAt("gisColumnName"); } if(!isClientExempt()) { GenUtils.ensureArray(data, "type"); List<Json> searchTypes = data.at("type").asJsonList(); boolean searchAllAllowed = "legacy:ServiceCase".equals(searchTypes.get(0).asString()); if(searchAllAllowed || searchTypes.size() > 1) { Set<OWLNamedIndividual> permittedTypes = Permissions. getAllowedObjectsOfClass( Permissions.BO_VIEW, owlClass("legacy:ServiceCase"), getUserActors()); List<String> permittedTypeList = new ArrayList<String>(); for(OWLNamedIndividual ind : permittedTypes) { String indIRIStr = ind.getIRI().toString(); if (searchAllAllowed || searchTypes.contains(Json.make(indIRIStr))) { permittedTypeList.add(indIRIStr); } } data.set("type", permittedTypeList); if(permittedTypeList.isEmpty()) return ko("Permission denied to all Service Request Types specified in search parameters"); } else if (searchTypes.size() == 1) { if (!Permissions.check(individual("BO_View"), individual(searchTypes.get(0).asString()), getUserActors())) return ko("Permission denied to Service Request Type specified in search parameters."); } else { return ko("No type indication was provided by Basic Search. Please contact tech team."); } } q = qt.translate(data, store); Set<Long> results = store.query(q, Refs.tempOntoManager.resolve().getOWLDataFactory()); if (results.size() > 0) { Sql select = SELECT(); Statement statement = new Statement(); Query query = new Query(); statement.setSql(select); query.setStatement(statement); select .COLUMN("a.SR_REQUEST_ID").AS("SR_REQUEST_ID") .COLUMN("i1.IRI").AS("TYPE") .COLUMN("addrV.FULL_ADDRESS").AS("FULL_ADDRESS") .COLUMN("addrV.ZIP").AS("ZIP") .COLUMN("addrV.CITY_SHORT").AS("CITY") .COLUMN("a.SR_STATUS").AS("STATUS") .COLUMN("acts.COMPLETE_DATE").AS("COMPLETE_DATE") .COLUMN("a.CREATED_DATE").AS("CREATED_DATE") .COLUMN("a.CASE_NUMBER").AS("CASE_NUMBER") .COLUMN("addrV.UNIT").AS("UNIT"); if (gisSearch.at("isPresent").asBoolean() == true) select.COLUMN( "CIRM_GIS_INFO." + gisSearch.at("gisColumnName").asString()) .AS("gisColumn"); select.FROM("CIRM_SR_REQUESTS a"); String innerQuery = "(SELECT DISTINCT a1.SR_REQUEST_ID, " + "MAX(a1.COMPLETE_DATE) AS COMPLETE_DATE FROM " + "CIRM_SR_ACTIVITY a1 GROUP BY a1.SR_REQUEST_ID) acts "; select.LEFT_OUTER_JOIN(innerQuery).ON("a.SR_REQUEST_ID", "acts.SR_REQUEST_ID"); select.LEFT_OUTER_JOIN("CIRM_MDC_ADDRESS_VIEW addrV").ON( "a.SR_REQUEST_ADDRESS", "addrV.ADDRESS_ID"); select.LEFT_OUTER_JOIN("CIRM_CLASSIFICATION cl").ON( "cl.SUBJECT", "a.SR_REQUEST_ID"); select.LEFT_OUTER_JOIN("CIRM_IRI i1") .ON("cl.OWLCLASS", "i1.ID"); select.LEFT_OUTER_JOIN("CIRM_GIS_INFO").ON("a.GIS_INFO_ID", "CIRM_GIS_INFO.ID"); select.WHERE("cl.TO_DATE IS NULL"); select.AND(); select.WHERE("a.SR_REQUEST_ID"); Set<String> boids = new HashSet<String>(); for (Long boid : results) boids.add(boid.toString()); select.IN(boids.toArray(new String[boids.size()])); Json viewResults = store.advancedSearch(query); for (Json j : viewResults.asJsonList()) { OWLNamedIndividual ind = individual("legacy:" + j.at("type").asString()); j.set("label", OWL.getEntityLabel(ind)); ind = individual(j.at("Street_Address_City").asString()); Set<OWLLiteral> dpSet = ind.getDataPropertyValues( dataProperty("Name"), topOntology.resolve()); if (!dpSet.isEmpty()) j.set("Street_Address_City", dpSet.iterator().next() .getLiteral()); else { dpSet = ind.getDataPropertyValues( dataProperty("Alias"), topOntology.resolve()); if (!dpSet.isEmpty()) j.set("Street_Address_City", dpSet.iterator() .next().getLiteral()); } resultsArray.add(j); } } return ok().set("resultsArray", resultsArray).set("totalRecords", getSearchResultCount(q, store)); } catch (Exception e) { e.printStackTrace(); return ko(e.getMessage()); } finally { if (DBG) ThreadLocalStopwatch.getWatch().time("END lookupAdvancedSearch"); ThreadLocalStopwatch.dispose(); } } public Json updateServiceCase(BOntology bontology) { // if(DBG) DBGUtils.printOntologyFunctional(bontology.getOntology()); // try // { getPersister().saveBusinessObjectOntology(bontology.getOntology()); return ok(); // } // Ticket #368 do not swallow exception here! // catch (Exception e) // { // e.printStackTrace(System.err); // return ko(e.getMessage()); // } } /** * UpdateHistoric allows updating a serviceCase in the past for exempt clients. * e.g. close a locked case at a date provided by a department. * * @param updatedServiceCase a prefixed service case json with updates applied. * @param updatedDateStr a date in ISO (Genutils) standard. * @return */ @POST @Path("updateHistoric") @Consumes("application/json") @Produces("application/json") public Json updateServiceCaseHistoric(Json updatedServiceCase, @QueryParam("updatedDate") final String updatedDateStr) { if (!isClientCirmAdmin()) { return ko("Permission denied for non CirmAdmin client."); } ThreadLocalStopwatch.startTop("START updateServiceCaseHistoric"); System.out.println(updatedServiceCase.toString()); Date updatedDate = GenUtils.parseDate(updatedDateStr); Json result = updateServiceCase(updatedServiceCase, updatedDate, "department"); if (result.is("ok", true)) { srStatsReporter.succeeded("updateServiceCaseHistoric restcall", updatedServiceCase); } else { srStatsReporter.failed("updateServiceCaseHistoric restcall", updatedServiceCase, result.at("error").toString(), result.at("stackTrace").toString()); } ThreadLocalStopwatch.stop("END updateServiceCaseHistoric"); return result; } @POST //@Encoded 2372 Java8 hilpold @Path("update") @Produces("application/json") public Json updateServiceCase(@FormParam("data") final String formData) { ThreadLocalStopwatch.startTop("START updateServiceCase data"); Json form = read(formData); // Not sure if this is needed anymore (Boris) if (form.at("properties").has("legacy:hasDepartmentError")) form.at("properties").delAt("legacy:hasDepartmentError"); if (!isClientExempt() && !Permissions.check(individual("BO_Update"), individual(form.at("type").asString()), getUserActors())) { ThreadLocalStopwatch.stop("DENIED updateServiceCase data"); return ko("Permission denied."); } else { Json result = updateServiceCase(form, "cirmuser"); if (result.is("ok", true)) { ThreadLocalStopwatch.stop("END updateServiceCase data"); srStatsReporter.succeeded("updateServiceCase restcall", form); } else { ThreadLocalStopwatch.fail("FAIL updateServiceCase data"); srStatsReporter.failed("updateServiceCase restcall", form, result.at("error").toString(), result.at("stackTrace").toString()); } return result; } } private Json findField(Json fields, String code) { for (Json f : fields.asJsonList()) if (f.at("hasServiceField").is("hasLegacyCode", code)) return f; return Json.nil(); } private boolean compareFieldValues(Json leftFields, String leftCode, Json rightFields, String rightCode) { Json left = findField(leftFields, leftCode); Json right = findField(rightFields, rightCode); if (left.equals(right)) return true; else return left.isObject() && right.isObject() && left.is("hasAnswerValue", right.at("hasAnswerValue")); } public boolean hasAddressUpdated(Json existing, Json newdata) { if (existing.at("properties").has("hasXCoordinate") || newdata.at("properties").has("hasXCoordinate")) if (!existing.at("properties").is("hasXCoordinate", newdata.at("properties").at("hasXCoordinate"))) return true; if (existing.at("properties").has("hasYCoordinate") || newdata.at("properties").has("hasYCoordinate")) if (!existing.at("properties").is("hasYCoordinate", newdata.at("properties").at("hasYCoordinate"))) return true; // FIXME: this address field by field comparison is not accurate because // sometimes the state will include the label, sometimes not so non-essential // differences are picked up. if (existing.at("properties").has("atAddress")) { if (newdata.at("properties").has("atAddress")) { Json x = OWL.resolveIris(existing, null).at("properties").at("atAddress").dup().delAt("iri"); Json y = OWL.resolveIris(newdata, null).at("properties").at("atAddress").dup().delAt("iri"); x = JsonUtil.apply(x, new JsonUtil.RemoveProperty("label")); y = JsonUtil.apply(y, new JsonUtil.RemoveProperty("label")); if (!x.equals(y)) return true; } } else if (newdata.at("properties").has("atAddress")) return true; Json lans = existing.at("properties").at("hasServiceAnswer"); Json rans = newdata.at("properties").at("hasServiceAnswer"); if (lans != null && rans != null) { if (!compareFieldValues(lans, "GIS2", rans, "GIS2") || !compareFieldValues(lans, "GIS3", rans, "GIS3") || !compareFieldValues(lans, "GIS4", rans, "GIS4") || !compareFieldValues(lans, "GIS5", rans, "GIS5")) return true; } return false; } public Json updateServiceCaseTransaction(final Json newValue, final Json existing, Date updatedDate, final List<CirmMessage> emailsToSend, final String originator) { final Json serviceCase = newValue.dup(); Json dbActs = array(); Json uiActs = array(); Json newActivities = array(); if (updatedDate == null) updatedDate = getPersister().getStore().getStoreTime(); serviceCase.at("properties").set("hasDateLastModified", GenUtils.formatDate(updatedDate)); final Json actorEmails = serviceCase.at("properties").atDel("actorEmails"); final Json hasRemovedAttachment = serviceCase.at("properties").atDel("hasRemovedAttachment"); Long boid = serviceCase.at("boid").asLong(); if (serviceCase.at("properties").has("legacy:hasServiceActivity")) for (Json a : serviceCase.at("properties").atDel("legacy:hasServiceActivity").asJsonList()) { if (a.has("iri")) uiActs.add(a); else if (!a.has("iri")) newActivities.add(a); } serviceCase.at("properties").set("legacy:hasServiceActivity", uiActs); // delete any removed Images tryDeleteAttachments(hasRemovedAttachment); //fetch DB Activities to compare/update against UI Activities //T2 if(uiActs.asJsonList().size() > 0) { if(existing.at("properties").at("hasServiceActivity").isArray()) dbActs = existing.at("properties").at("hasServiceActivity"); else dbActs.add(existing.at("properties").at("hasServiceActivity")); } final BOntology bontology = BOntology.makeRuntimeBOntology(serviceCase); ActivityManager mngr = new ActivityManager(); OWLNamedIndividual currentStatus = individual(existing.at("properties"),"hasStatus"); OWLNamedIndividual newStatus = individual(serviceCase.at("properties"),"legacy:hasStatus"); OWLLiteral srModifiedBy = bontology.getDataProperty("isModifiedBy"); ///T2 END //06-20-2013 syed - Check for a status change. if(currentStatus != null && !currentStatus.equals(newStatus)) { if ("cirmuser".equals(originator) && currentStatus.equals(individual("legacy:O-LOCKED")) && newStatus.equals(individual("legacy:O-OPEN"))) { //2016.07.08 hilpold mdcirm 2639 // We prevent a very specific concurrent SR modification problem: // only if a user saves an SR in OPEN status, which is already locked, we prevent // an overwrite of interface changes. throw new ConcurrentLockedToOpenException(); } else { mngr.changeStatus(currentStatus, newStatus, updatedDate, (srModifiedBy != null)?srModifiedBy.getLiteral():null, bontology, emailsToSend); if (individual("legacy:O-LOCKED").equals(newStatus)) { mngr.createAutoOnLockedActivities(bontology, new Date(), emailsToSend); } } } //Update those existing Activities for which Outcome is set in current request if(uiActs.asJsonList().size() > 0) updateExistingActivities(uiActs, dbActs, mngr, bontology, boid, emailsToSend); String srModifiedByStr = srModifiedBy == null? null : srModifiedBy.getLiteral(); //06-20-2013 syed - set the createdBy to the SR modifier. for (final Json eachActivity : newActivities.asJsonList()) { final OWLNamedIndividual activity = individual(eachActivity.at("legacy:hasActivity") .at("iri").asString()); String details = eachActivity.has("legacy:hasDetails") ? eachActivity .at("legacy:hasDetails").asString() : null; String assignedTo = eachActivity.has("legacy:isAssignedTo") ? eachActivity .at("legacy:isAssignedTo").asString() : null; String actCreatedBy = eachActivity.has("isCreatedBy") ? eachActivity .at("isCreatedBy").asString() : srModifiedByStr; Json hasOutcome = eachActivity.has("legacy:hasOutcome") ? eachActivity .at("legacy:hasOutcome") : null; java.util.Date createdDate = eachActivity.has("hasDateCreated") ? GenUtils.parseDate(eachActivity.at("hasDateCreated").asString()) : null; java.util.Date completedDate = eachActivity.has("legacy:hasCompletedTimestamp") ? GenUtils.parseDate(eachActivity.at("legacy:hasCompletedTimestamp").asString()) : null; if (hasOutcome == null && completedDate == null) mngr.createActivity(activity, details, assignedTo, bontology, createdDate, actCreatedBy, emailsToSend); else { OWLNamedIndividual outcome = hasOutcome != null ? individual(hasOutcome.at("iri").asString()) : null; mngr.createActivity(activity, outcome, details, assignedTo, bontology, createdDate, completedDate, actCreatedBy, emailsToSend); } if (!eachActivity.has("legacy:hasOutcome") && eachActivity.is("legacy:isAccepted", true)) { OWLNamedIndividual activityTypeInd = individual(eachActivity.at("legacy:hasActivity").at("iri").asString()); Set<OWLNamedIndividual> outcomes = reasoner().getObjectPropertyValues( activityTypeInd, objectProperty("legacy:hasDefaultOutcome")).getFlattened(); if(outcomes.size() > 0) { eachActivity.set("hasOutcome", OWL.toJSON(outcomes.iterator().next())); } } CirmTransaction.get().addTopLevelEventListener(new CirmTransactionListener() { public void transactionStateChanged(final CirmTransactionEvent e) { if (e.isSucceeded()) EventDispatcher.get().dispatch( OWL.individual("legacy:ServiceCaseNewActivityEvent"), activity, OWL.individual("BO_New"), Json.object("serviceCase", serviceCase, "activity", eachActivity, "originator", originator)); } }); } if (hasAddressUpdated(existing, newValue)) populateGisData(serviceCase, bontology); Json updateResult = updateServiceCase(bontology); if (updateResult.is("ok", true)) { Json result = bontology.toJSON(); addAddressData(result); //Register Top Level Tx fire Update event CirmTransaction.get().addTopLevelEventListener(new CirmTransactionListener() { public void transactionStateChanged(final CirmTransactionEvent e) { if (e.isSucceeded()) { try { EventDispatcher.get().dispatch( OWL.individual("legacy:ServiceCaseUpdateEvent"), bontology.getBusinessObject(), OWL.individual("BO_Update"), Json.object("case", result.dup())); } catch (Exception ex) { ThreadLocalStopwatch.error("Error updateServiceCaseTransaction - Failed to dispatch update event for " + bontology.getObjectId()); ex.printStackTrace(); } } } }); // //START : Emails to customers if(actorEmails != null) { sendEmailToCustomers(bontology, actorEmails.asJsonList(), emailsToSend, result.at("properties").at("hasCaseNumber").asString(), OWL.getEntityLabel(individual("legacy:"+result.at("type").asString())) ); } //END : Emails to customers return ok().set("bo", result); } else return updateResult; } public Json updateServiceCase(final Json serviceCaseParam, final String originator) { return updateServiceCase(serviceCaseParam, null, originator); } public Json updateServiceCase(final Json serviceCaseParam, final Date updateDate, final String originator) { if (originator == null) throw new NullPointerException("originator"); //wrap the entire update in a transaction block. try { ThreadLocalStopwatch.start("START updateServiceCase (str)"); final List<CirmMessage> emailsToSend = new ArrayList<CirmMessage>(); Json result = Refs.defaultRelationalStore.resolve().txn(new CirmTransaction<Json>() { public Json call() { emailsToSend.clear(); CirmTransaction.get().addTopLevelEventListener(new SendEmailOnTxSuccessListener(emailsToSend)); //Saving the current context as we are losing when //using another get/post internally Response current = Response.getCurrent(); Long boid = serviceCaseParam.at("boid").asLong(); Json bo = findServiceCaseOntology(boid).toJSON(); Json result = updateServiceCaseTransaction(serviceCaseParam, bo, updateDate, emailsToSend, originator); Response.setCurrent(current); return result; }}); ThreadLocalStopwatch.stop("END updateServiceCase (str)"); return result; } catch (Throwable e) { if (CirmTransaction.isExecutingOnThisThread()) { //We're still inside a higher level transaction and must not hide/catch a potentially retriable exception. throw e; } else { //Top level transaction completed, all possible retries completed, return error json. ThreadLocalStopwatch.fail("FAIL updateServiceCase (str)"); System.out.println("formData passed into updateServiceCase: "+ serviceCaseParam.toString()); e.printStackTrace(); return ko(e); } } } /** * Augment a business object ontology with metadata axioms. * */ public BOntology addMetaDataAxioms(BOntology bo) throws OWLOntologyCreationException { OWLOntology ontology = bo.getOntology(); IRI verboseiri = IRI.create(ontology.getOntologyID().getOntologyIRI().toString() + "/verbose"); OWLOntologyManager manager = Refs.tempOntoManager.resolve(); OWLOntology result = manager.getOntology(verboseiri); if (result != null) manager.removeOntology(result); result = manager.createOntology( IRI.create(ontology.getOntologyID().getOntologyIRI().toString() + "/verbose"), Collections.singleton(ontology)); OWLDataFactory factory = manager.getOWLDataFactory(); Set<OWLNamedIndividual> individuals = result.getIndividualsInSignature(); List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>(); for (OWLNamedIndividual ind : individuals) { // Idk if this is the best way to check if an individual is declared // in meta. // OWLReasoner doesn't provide facilities to retrieve axioms for an // individual. // and for now we are only interested in adding legacy axioms. // If the individual lives in the CiRM namespace, we add all information about it. if (OWL.ontology().containsEntityInSignature(ind, true)) { ind = OWL.individual(ind.getIRI()); for (OWLOntology O : OWL.ontologies()) { for (OWLIndividualAxiom axiom : O.getDataPropertyAssertionAxioms(ind)) changes.add(new AddAxiom(result, axiom)); for (OWLObjectPropertyAssertionAxiom axiom : O.getObjectPropertyAssertionAxioms(ind)) // I'm not sure why we are skipping those two properties. Perhaps they are not needed // but are they harmful? That logic takes away the generality of the method. if (!axiom.getProperty().equals(objectProperty("legacy:hasLegacyInterface")) && !axiom.getProperty().equals(objectProperty("legacy:hasAllowableEvent"))) changes.add(new AddAxiom(result, axiom)); } } else { // add boid to businessObject in the BOntology OWLDataProperty boid = factory.getOWLDataProperty(Model.legacy("boid")); if (ind.getDataPropertyValues(boid, result).isEmpty()) { Long id = Long.valueOf(bo.getObjectId()); // identifiers.get(ind); // 1-15-2013 save the round trip to the DB and grab the id // from the onto. if (id != null) changes.add(new AddAxiom( result, factory.getOWLDataPropertyAssertionAxiom( boid, ind, factory.getOWLLiteral( id.toString(), factory.getOWLDatatype(OWL2Datatype.XSD_INT .getIRI()))))); } } } if (changes.size() > 0) manager.applyChanges(changes); BOntology newBO = new BOntology(result); return newBO; } /** * If an outcome is set for any EXISTING ACTIVITY, then have to * update that particular Activity via ActivityManager.updateActivity * * EXISTING ACTIVITY : An Activity which is already present when the * user loads the Service Request in the UI. * * @param uiActivities : List of all existing Activities from UI * @param dbActivities : List of all existing Activities from DB * @param manager : ActivityManager * @param bontology : The Service Request's business ontology * @param boid : Unique identifier of the Service Request */ private void updateExistingActivities( Json uiActivities, Json dbActivities, ActivityManager manager, BOntology bontology, Long boid, List<CirmMessage> messages) { for(Json dbAct : dbActivities.asJsonList()) { String dbActIRI = dbAct.at("iri").asString(); String dbOutcomeIRI = dbAct.has("hasOutcome") ? dbAct.at("hasOutcome").isObject() ? dbAct.at("hasOutcome").at("iri").asString() : dbAct.at("hasOutcome").asString() : null; for(Json uiAct : uiActivities.asJsonList()) { String uiActIRI = uiAct.at("iri").asString(); String activityType; if (uiAct.at("legacy:hasActivity").isObject()) { activityType = uiAct.at("legacy:hasActivity").at("iri").asString(); } else { activityType = uiAct.at("legacy:hasActivity").asString(); } String modifiedBy = uiAct.has("isModifiedBy") ? uiAct.at("isModifiedBy").asString() : null; if(dbActIRI.equals(uiActIRI)) { String uiOutcomeIRI; if (uiAct.has("legacy:hasOutcome")) { if (uiAct.at("legacy:hasOutcome").isObject()) { uiOutcomeIRI = uiAct.at("legacy:hasOutcome").at("iri").asString(); } else { uiOutcomeIRI = uiAct.at("legacy:hasOutcome").asString(); } } else { uiOutcomeIRI = null; } String details = uiAct.has("legacy:hasDetails") ? uiAct.at("legacy:hasDetails").asString() : null; String assignedTo = uiAct.has("legacy:isAssignedTo") ? uiAct.at("legacy:isAssignedTo").asString() : null; boolean isAccepted = uiAct.has("legacy:isAccepted") ? uiAct.at("legacy:isAccepted").toString().equals("true") : false; if(dbOutcomeIRI == null) { if((uiOutcomeIRI == null && isAccepted) || uiOutcomeIRI != null) { manager.updateActivity(activityType, uiActIRI, uiOutcomeIRI, details, assignedTo, modifiedBy, isAccepted, bontology, messages); } } else continue; } else continue; } } } /** * Try to delete attachments (files and images) if serviceCase json has a hasRemovedAttachment property.<br> * Always called inside of a transaction. * @param serviceCase */ public void tryDeleteAttachments(Json hasRemovedAttachment) { if(hasRemovedAttachment != null && hasRemovedAttachment.isArray()) { List<Json> hasRemovedAttachmentList = hasRemovedAttachment.asJsonList(); if (hasRemovedAttachmentList.size() > 0) { CirmTransaction.get().addTopLevelEventListener(new RemoveAttachmentsOnTxSuccessListener(hasRemovedAttachmentList)); } } else { //programming error } } public Json populateGisData(Json legacyForm, BOntology bontology) { // Checking for x and y properties before adding as Address it not // mandatory anymore if (legacyForm.at("properties").has("hasXCoordinate") && legacyForm.at("properties").has("hasYCoordinate")) { if (DBG) ThreadLocalStopwatch.getWatch().time("START populateGisData"); Json locationInfo = Refs.gisClient.resolve().getLocationInfo( legacyForm.at("properties").at("hasXCoordinate") .asDouble(), legacyForm.at("properties") .at("hasYCoordinate").asDouble(), null); Json scase = ontoToJson(bontology); if (legacyForm.at("properties").has("hasLegacyInterface")) scase.at("properties").set("hasLegacyInterface", legacyForm.at("properties").at("hasLegacyInterface")); Json extendedInfo = Json.nil(); try { extendedInfo = Refs.gisClient.resolve().getInformationForCase(scase); } catch (Throwable t) { extendedInfo = Json.object().set("error", t.toString()); } if (!extendedInfo.isNull()) locationInfo.set("extendedInfo", extendedInfo); else locationInfo.delAt("extendedInfo"); Json gisLiteral = Json.make(GisDAO.getGisDBId(locationInfo, false)); OWLLiteral owlLiteral = bontology.getDataProperty( bontology.getBusinessObject(), "legacy:hasGisDataId"); if(owlLiteral != null) { bontology.deleteDataProperty( bontology.getBusinessObject(), dataProperty("legacy:hasGisDataId")); } bontology.addDataProperty( bontology.getBusinessObject(), dataProperty("legacy:hasGisDataId"), Json.object() .set("literal", gisLiteral.asLong()) .set("type", "http://www.w3.org/2001/XMLSchema#integer")); if (DBG) ThreadLocalStopwatch.getWatch().time("END populateGisData"); return locationInfo; } return Json.nil(); } /** * Endpoint that creates a case number (Exempt client only) * @return json object with property hasCaseNumber : String */ @POST @Path("createNewCaseNumber") @Produces("application/json") public synchronized Json createNewCaseNumber() { if (!isClientExempt()) { return GenUtils.ko("Not authorized."); } String newCaseNumber = getPersister().getStore().txn(new CirmTransaction<String> () { @Override public String call() throws Exception { DBIDFactory idFactory = (DBIDFactory) Refs.idFactory.resolve(); long seq = idFactory.generateUserFriendlySequence(); return GenUtils.makeCaseNumber(seq); } }); return GenUtils.ok().set("newCaseNumber", newCaseNumber); } /** * Adds the Case Number as a dataProperty in the bo * @param legacyForm : bo in Json format */ private void createCaseNumber(Json legacyForm) { long seq = ((DBIDFactory) Refs.idFactory.resolve()) .generateUserFriendlySequence(); legacyForm.at("properties").set("legacy:hasCaseNumber", GenUtils.makeCaseNumber(seq)); } /** * Creates a new Service Request coming in from the UI. * * @param formData : The Service Request data stringified json * @return : returns the Business Ontology which is persisted to db in Json format */ @POST //@Encoded 2372 Java8 hilpold @Path("kosubmit") @Produces("application/json") public Json createNewKOSR(@FormParam("data") final String formData) { ThreadLocalStopwatch.startTop("START createNewKOSR"); final Json legacyForm = read(formData); final boolean hasCaseNumber = legacyForm.has("properties") && legacyForm.at("properties").has("legacy:hasCaseNumber") && legacyForm.at("properties").at("legacy:hasCaseNumber").isString(); try { // System.out.println("new SR to save: " + legacyForm); final Json actorEmails = legacyForm.at("properties").atDel("actorEmails"); final Json hasRemovedAttachment = legacyForm.at("properties").atDel("hasRemovedAttachment"); final String type = legacyForm.at("type").asString(); if (!isClientExempt() && !Permissions.check(individual("BO_New"), individual(type), getUserActors())) return ko("Permission denied."); //TODO PUT GIS INFO CALL OUTSIDE TRANSACTION!!! //DB, but sequence only, rollback no effect on sequences. getPersister().getStore().txn(new CirmTransaction<Object> () { @Override public Object call() throws Exception { legacyForm.set("boid", Refs.idFactory.resolve().newId(null)); if (!hasCaseNumber) { createCaseNumber(legacyForm); } return null; } } ); final List<CirmMessage> emailsToSend = new ArrayList<CirmMessage>(); final Json locationInfo = Json.object(); // BO, def activities, email, saveBo, IFaces, Json result = getPersister().getStore().txn(new CirmTransaction<Json> () { @Override public Json call() throws Exception { tryDeleteAttachments(hasRemovedAttachment); final BOntology bontology = BOntology.makeRuntimeBOntology(legacyForm); //Saving the current context as we are losing when //using another get/post internally. Response current = Response.getCurrent(); ThreadLocalStopwatch.now("START createDefaultActivities"); ActivityManager am = new ActivityManager(); //TODO hilpold move this to where emails are created emailsToSend.clear(); CirmTransaction.get().addTopLevelEventListener(new SendEmailOnTxSuccessListener(emailsToSend)); am.createDefaultActivities(owlClass(type), bontology, GenUtils.parseDate(legacyForm.at("properties").at("hasDateCreated").asString()), emailsToSend); Response.setCurrent(current); ThreadLocalStopwatch.now("END createDefaultActivities"); Json locationInfoTmp = populateGisData(legacyForm, bontology); if (!locationInfoTmp.isNull()) locationInfo.with(locationInfoTmp); //validateResources(legacyForm, bontology); //DB getPersister().saveBusinessObjectOntology(bontology.getOntology()); // delete any removed Images, if save succeeds only OWLNamedIndividual emailTemplate = objectProperty(individual(type), "legacy:hasEmailTemplate"); // It's an array only because it has to be a final variable, so we can insert or not. final ArrayList<BOntology> withMetadata = new ArrayList<BOntology>(); if (emailTemplate != null) { try { BOntology withMeta = addMetaDataAxioms(bontology); withMetadata.add(withMeta); CirmMessage msg = MessageManager.get().createMessageFromTemplate( withMeta, dataProperty(individual(type), "legacy:hasLegacyCode"), emailTemplate); msg.addExplanation("createNewKOSR SR template " + emailTemplate.getIRI().getFragment()); emailsToSend.add(msg); } catch (Throwable t) { ThreadLocalStopwatch.error("Error createNewKOSR - Failed to create email for " + bontology.getObjectId()); } } final Json result = bontology.toJSON(); addAddressData(result); CirmTransaction.get().addTopLevelEventListener(new CirmTransactionListener() { public void transactionStateChanged(final CirmTransactionEvent e) { if (e.isSucceeded()) { try { BOntology withMeta = withMetadata.isEmpty() ? LegacyEmulator.this.addMetaDataAxioms(bontology):withMetadata.get(0); EventDispatcher.get().dispatch( OWL.individual("legacy:NewServiceCaseEvent"), bontology.getBusinessObject(), OWL.individual("BO_New"), Json.object("case", OWL.toJSON(withMeta.getOntology(), bontology.getBusinessObject()), "locationInfo", locationInfo)); } catch (Exception ex) { GenUtils.reportFatal("Failed to send case " + bontology.getObjectId() + " to department", "", ex); ThreadLocalStopwatch.error("Error createNewKOSR - Failed to send to dept " + bontology.getObjectId()); } } } }); //START : Emails to customers if(actorEmails != null) { sendEmailToCustomers(bontology, actorEmails.asJsonList(), emailsToSend, result.at("properties").at("hasCaseNumber").asString(), OWL.getEntityLabel(individual("legacy:"+result.at("type").asString())) ); } //END : Emails to customers return ok().set("bo", result); } }); if (locationInfo.has("extendedInfo") && locationInfo.at("extendedInfo").has("error")) { GenUtils.reportPWGisProblem(result.at("bo").at("properties").at("hasCaseNumber").asString(), locationInfo.at("extendedInfo").at("error")); ThreadLocalStopwatch.error("Error createNewKOSR - Gis problem " + result.at("bo").at("properties").at("hasCaseNumber").asString()); } //MessageManager.get().sendEmails(emailsToSend); srStatsReporter.succeeded("create", legacyForm); ThreadLocalStopwatch.stop("END createNewKOSR"); return result; } catch (Throwable e) { Throwable t = GenUtils.getRootCause(e); srStatsReporter.failed("create", legacyForm, e.toString() + "Rootcause: " + t.toString(), e.getMessage() + "Root Msg: " + t.getMessage()); ThreadLocalStopwatch.fail("FAIL createNewKOSR"); System.out.println("formData passed into createNewKOSR : "+formData); e.printStackTrace(); return ko(e.getMessage()); } } private void sendEmailToCustomers(BOntology bo, List<Json> customers, List<CirmMessage> emailsToSend, String caseNumber, String srType) { for(Json customer : customers) { CirmMessage msg = MessageManager.get().createMessageFromTemplate( bo, individual("legacy:SERVICEHUB_EMAIL_CUSTOMERS"), srType + " " + caseNumber, customer.at("email").asString(), null, null, null); // "Dear "+customer.at("name").asString() // +", \n We successfully processed the Service Request."); emailsToSend.add(msg); } } /** * Saves a new non Cirm originated (e.g. PW, CMS Interfaces, Open311) or referral service case into the cirm database. * If the SR is saved in pending state autoOnPending activities may be created and emails be sent. * * Must be called from within a CirmTransaction. * @param legacyform * @return */ public Json saveNewCaseTransaction(Json legacyForm) { boolean hasCaseNumber = legacyForm.has("properties") && legacyForm.at("properties").has("legacy:hasCaseNumber") && legacyForm.at("properties").at("legacy:hasCaseNumber").isString(); OperationService op = new OperationService(); // 1 Determine SR type and create a new case number String type = legacyForm.at("type").asString(); int i = type.indexOf("legacy:"); if (i == -1) type = "legacy:" + type; BOntology bontology = op.createBusinessObject(owlClass(type)); legacyForm.set("boid", bontology.getObjectId()); if (!hasCaseNumber) { createCaseNumber(legacyForm); } // 2 Parse all legacyForm data into a business ontology bontology = BOntology.makeRuntimeBOntology(legacyForm); populateGisData(legacyForm, bontology); //3 Check pending and create autoOnPending activities if (OWL.individual("legacy:O-PENDNG").equals(bontology.getObjectProperty("legacy:hasStatus"))) { List<CirmMessage> messages = new ArrayList<>(); ActivityManager amgr = new ActivityManager(); amgr.createAutoOnPendingActivities(bontology, new Date(), messages); CirmTransaction.get().addTopLevelEventListener(new SendEmailOnTxSuccessListener(messages)); } //4 Save BO getPersister().saveBusinessObjectOntology(bontology.getOntology()); // 5 Extend BO by adding meta data axioms final BOntology bontologyVerbose; try { bontologyVerbose = addMetaDataAxioms(bontology); } catch (OWLOntologyCreationException e) { throw new RuntimeException(e); } final Json bontologyVerboseJson = OWL.toJSON(bontologyVerbose.getOntology(), bontology.getBusinessObject()); bontologyVerboseJson.set("boid", bontology.getObjectId()); // 6 Fire a legacy:NewServiceCaseExternalEvent only if the overall transaction finishes successfully. // This ignores retries (we're inside a repeatable transaction!), so the event is guaranteed to fire only once. // Assumes access to final variables bontologyVerboseJson, bontologyVerbose during event processing, // so we must not change the json bontologyVerboseJson points to after returning it. CirmTransaction.get().addTopLevelEventListener(new CirmTransactionListener() { public void transactionStateChanged(final CirmTransactionEvent e) { if (e.isSucceeded()) { try { EventDispatcher.get().dispatch( OWL.individual("legacy:NewServiceCaseNonCirmEvent"), bontologyVerbose.getBusinessObject(), OWL.individual("BO_New"), Json.object("case", bontologyVerboseJson)); // maybe add "locationInfo", locationInfo } catch (Exception ex) { String errmsg ="Failure during the dispatch of a legacy:NewServiceCaseExternalEvent for " + bontologyVerbose.getObjectId(); ThreadLocalStopwatch.error(errmsg); } } } }); return ok().set("data", bontologyVerboseJson); } /** * Internal method (no permissions check) to save a new service request. * * @param formData * @return */ public Json saveNewServiceRequest(final String formData) { ThreadLocalStopwatch.start("START saveNewServiceRequest (referral?)"); try { return Refs.defaultRelationalStore.resolve().txn(new CirmTransaction<Json>() { public Json call() { Json result = saveNewCaseTransaction(Json.read(formData)); ThreadLocalStopwatch.stop("END saveNewServiceRequest (referral?)"); return result; }}); } catch (Exception e) { ThreadLocalStopwatch.fail("FAIL saveNewServiceRequest (referral?) with " + e); e.printStackTrace(); return ko(e); } } private Set<OWLNamedIndividual> fetchIndividualSet(String csrTypeIRI) { if (csrTypeIRI != null && !csrTypeIRI.contains("legacy:")) csrTypeIRI = "legacy:" + csrTypeIRI; String classExpr = "legacy:QuestionTrigger and legacy:hasServiceField some " + "(inverse legacy:hasServiceField value " + csrTypeIRI + ")"; return OWL.queryIndividuals(classExpr); } /** * Emails the Service Request to the listed recipients * @param formData : A JSON object with email subject and recipients * eg : formData = { * "subject":"Test email", * "to":"abc@abc.com;xyz@xyz.net", * "cc":"abc@abc.gov;xyz@xyz.net", * "bcc":"abc@abc.net" * "boid": 12345678 * } * @return : a status message if successful or not. */ @POST //@Encoded 2372 Java8 hilpold @Path("emailSR") @Produces("application/json") public Json emailServiceRequestTo(@FormParam("data") final String formData) { if (DBG) ThreadLocalStopwatch.getWatch().time("START emailServiceRequestTo"); try { Json data = Json.read(formData); Long boid = data.at("boid").asLong(); String subject = data.has("subject") ? data.at("subject").asString() : null; String to = data.has("to") ? data.at("to").asString() : null; String cc = data.has("cc") ? data.at("cc").asString() : null; String bcc = data.has("bcc") ? data.at("bcc").asString() : null; String comments = data.has("comments")? data.at("comments").asString() : null; BOntology bo = findServiceCaseOntology(boid); final List<CirmMessage> emailsToSend = new ArrayList<CirmMessage>(); OWLNamedIndividual emailTemplate = individual("legacy:SERVICEHUB_EMAIL"); CirmMessage msg = MessageManager.get().createMessageFromTemplate( bo, emailTemplate, subject, to, cc, bcc,comments); emailsToSend.add(msg); MessageManager.get().sendEmails(emailsToSend); return ok(); } catch(Exception e) { e.printStackTrace(); return ko(e.getMessage()); } finally { if (DBG) ThreadLocalStopwatch.getWatch().time( "END emailServiceRequestTo"); ThreadLocalStopwatch.dispose(); } } @GET @Path("/alerts/{individual}") @Produces("application/json") public Json getAlerts(@PathParam("individual") String csrTypeIRI) { if (!isClientExempt() && !Permissions.check(individual("BO_View"), individual("legacy:" + csrTypeIRI), getUserActors())) return ko("Permission denied."); Json alerts = object(); Set<OWLNamedIndividual> indSet = fetchIndividualSet(csrTypeIRI); if (indSet == null) return ko("null"); else { for (OWLNamedIndividual ind : indSet) { String outerKey = null; String innerKey = dataProperty(ind, "legacy:hasAnswerValue") .getLiteral(); OWLNamedIndividual propInd = objectProperty(ind, "legacy:hasServiceField"); if (propInd != null) outerKey = propInd.getIRI().toString(); propInd = objectProperty(ind, "legacy:hasLegacyEvent"); if (propInd != null) { String label = OWL.getEntityLabel(propInd); if (label != null) { if (alerts.has(outerKey)) alerts.at(outerKey).set(innerKey, label); else alerts.set(outerKey, object().set(innerKey, label)); } } } Json hasAlerts = object().set("hasAlerts", alerts); return hasAlerts; } } @GET @Path("/bo/{boid}/dom") @Produces("application/json") public Json getServiceCaseAsDom(@PathParam("boid") Long boid) { try { OperationService op = new OperationService(); BOntology bo = op.getBusinessObjectOntology(boid); if (bo != null && bo.getBusinessObject() != null) { if (!isClientExempt() && !Permissions.check(individual("BO_View"), individual(bo.getTypeIRI("legacy")), getUserActors())) return ko("Permission denied."); MessageManager manager = new MessageManager(); BOntology verbose = addMetaDataAxioms(bo); Json sr = OWL.toJSON(verbose.getOntology(), verbose.getBusinessObject()); return ok().set("data", manager.toDOMString(sr)); } else { return ko("Could not retrieve the ontology for id:" + boid); } } catch (Exception e) { e.printStackTrace(); return ko(e); } } @GET @Path("/bo/{boid}/activities") @Produces("application/json") public Json getActivities(@PathParam("boid") Long boid) { try { OperationService op = new OperationService(); BOntology bo = op.getBusinessObjectOntology(boid); if (bo != null && bo.getBusinessObject() != null) { if (!isClientExempt() && !Permissions.check(individual("BO_View"), individual(bo.getTypeIRI("legacy")), getUserActors())) return ko("Permission denied."); OWLOntology o = bo.getOntology(); Json array = Json.array(); for (OWLNamedIndividual activity : OWL .reasoner(o) .getObjectPropertyValues(bo.getBusinessObject(), objectProperty("legacy:hasServiceActivity")) .getFlattened()) { array.add(OWL.toJSON(o, activity)); } return array; } return ko("No object found with id " + boid); } catch (Throwable e) { e.printStackTrace(); return ko(e); } } /** * Creates an activity now, ignoring possible occur day settings. * @param boid * @param activityCode * @return */ @GET @Path("/bo/{boid}/activities/create/{activityCode}") @Produces("application/json") public Json createActivityNow(@PathParam("boid") Long boid, @PathParam("activityCode") String activityCode) { try { ThreadLocalStopwatch.startTop("START /bo/{boid}/activities/create/{activityCode} " + boid + " " + activityCode); OperationService op = new OperationService(); RelationalOWLPersister persister = getPersister(); BOntology bo = op.getBusinessObjectOntology(boid); if (bo != null && bo.getBusinessObject() != null) { if (!isClientExempt() && !Permissions.check(individual("BO_Update"), individual(bo.getTypeIRI("legacy")), getUserActors())) { ThreadLocalStopwatch.fail("Fail /bo/{boid}/activities/create/{activityCode} permission denied " + boid + " " + activityCode); return ko("Permission denied."); } // OWLOntology o = bo.getOntology(); OWLNamedIndividual status = bo .getObjectProperty("legacy:hasStatus"); if (status == null || status.getIRI().getFragment().startsWith("C-")) return ko("Can only create an activity on an open SR."); ActivityManager manager = new ActivityManager(); OWLNamedIndividual activity = individual("legacy:" + activityCode); //hilpold whole algorithm should be inside a transaction and SendEmailOnTxSuccessListener used List<CirmMessage> emailsToSend = new ArrayList<CirmMessage>(); //Create the activity ignoring occur day settings on TM callback. manager.createActivityOccurNow(activity, bo, emailsToSend); persister.saveBusinessObjectOntology(bo.getOntology()); for (CirmMessage m : emailsToSend) { m.addExplanation("LE.createActivity " + activityCode); } MessageManager.get().sendEmails(emailsToSend); ThreadLocalStopwatch.stop("END /bo/{boid}/activities/create/{activityCode} success " + boid + " " + activityCode); return ok(); } else { //Case not found ThreadLocalStopwatch.fail("FAIL /bo/{boid}/activities/create/{activityCode} case not found, but ok() returned " + boid + " " + activityCode); return ok(); } } catch (Throwable e) { ThreadLocalStopwatch.fail("FAIL /bo/{boid}/activities/create/{activityCode} " + boid + " " + activityCode + " with " + e); e.printStackTrace(); return ko(e); } } @GET @Path("/bo/{boid}/activity/{activityFragment}") @Produces("application/json") public Json getActivity(@PathParam("boid") Long boid, @PathParam("activityFragment") String activityFragment) { try { OperationService op = new OperationService(); BOntology bo = op.getBusinessObjectOntology(boid); if (bo != null && bo.getBusinessObject() != null) { if (!isClientExempt() && !Permissions.check(individual("BO_View"), individual(bo.getTypeIRI("legacy")), getUserActors())) return ko("Permission denied."); OWLOntology o = bo.getOntology(); Json object = null; for (OWLNamedIndividual activity : OWL .reasoner(o) .getInstances(OWL.oneOf(individual(activityFragment)), true).getFlattened()) { object = OWL.toJSON(o, activity); return object; } } return ko("No object found with id " + boid); } catch (Throwable e) { return ko(e); } } @GET @Path("/bo/{boid}/activity/{activityFragment}/overdue/create/{activityCode}") @Produces("application/json") public Json createActivityWhenOverdue(@PathParam("boid") Long boid, @PathParam("activityFragment") String activityFragment, @PathParam("activityCode") String overdueActivity) { String callInfo = "/bo/{boid}/activity/{activityFragment}/overdue/create/{activityCode} " + boid + " " + activityFragment + " " + overdueActivity; ThreadLocalStopwatch.startTop("START " + callInfo); try { OperationService op = new OperationService(); RelationalOWLPersister persister = getPersister(); BOntology bo = op.getBusinessObjectOntology(boid); if (bo != null && bo.getBusinessObject() != null) { if (!isClientExempt() && !Permissions.check(individual("BO_Update"), individual(bo.getTypeIRI("legacy")), getUserActors())) { ThreadLocalStopwatch.fail("FAIL permission denied " + callInfo); return ko("Permission denied."); } OWLOntology o = bo.getOntology(); OWLNamedIndividual activityToCheck = o.getOWLOntologyManager().getOWLDataFactory().getOWLNamedIndividual(OWL.fullIri(activityFragment)); if (o.getIndividualsInSignature(true).contains(activityToCheck)) { OWLNamedIndividual status = bo.getObjectProperty("legacy:hasStatus"); // if status is closed do nothing. if (status.getIRI().getFragment().startsWith("C-") || (bo.getDataProperty(activityToCheck, "legacy:hasCompletedDate") != null) || (bo.getDataProperty(activityToCheck, "legacy:hasCompletedTimestamp") != null)) return ok(); OWLLiteral dueDate = bo.getDataProperty(activityToCheck, "legacy:hasDueDate"); if (dueDate != null ) { Date due = OWL.parseDate(dueDate); Date now = new Date(); if (now.after(due)) { ActivityManager manager = new ActivityManager(); //TODO hilpold full method should be inside a transaction and SendEmailOnTxSuccessListener used List<CirmMessage> emailsToSend = new ArrayList<CirmMessage>(); manager.createActivity(individual("legacy:" + overdueActivity), null, null, bo, null, null, emailsToSend); manager.updateActivityIfAutoDefaultOutcome(activityToCheck, bo, emailsToSend); persister.saveBusinessObjectOntology(bo.getOntology()); for (CirmMessage m : emailsToSend) m.addExplanation("LE.createWhenOverDue boid " + boid + " ACt: " + activityFragment); MessageManager.get().sendEmails(emailsToSend); } } ThreadLocalStopwatch.stop("END " + callInfo); return ok(); } ThreadLocalStopwatch.fail("FAIL orig activity not found " + callInfo); return ko("Could not create overdue activity, original activity not found."); } ThreadLocalStopwatch.fail("FAIL bo not found " + callInfo); return ko("No business object found with id " + boid); } catch (Throwable e) { ThreadLocalStopwatch.fail("FAIL with " + e + " " + callInfo); e.printStackTrace(); return ko(e); } } /** * Calls a web service (retries up to MAX_CALLWS_ATTEMPS == 3) * @param type the type of web service to call (see ontologies) * @param arguments string array of arguments * @return ok or ko */ @GET @Path("/ws/{type}") @Produces("application/json") public Json callWS(@PathParam("type") String type, @QueryParam("arg") String[] arguments) { boolean failed; Json result; int attempt = 0; ThreadLocalStopwatch.startTop("START CallWS: " + type); do { attempt ++; failed = false; result = callWsImpl(type, arguments); if (result.is("ok", false)) { //Any Exception is fully caught and printed in callWsImpl failed = true; try { // sleep min 0.1, avg 0.3, max 0.5s secs Thread.sleep(((long)(100 + Math.random() * 400))); } catch (InterruptedException ie) {} } } while (failed && attempt < MAX_CALLWS_ATTEMPTS); if (result.is("ok", true)) { ThreadLocalStopwatch.stop("END CallWS: Success after " + attempt + " attempt(s)."); } else { ThreadLocalStopwatch.fail("ERROR CallWS: All 3 attempts failed, responding ko"); } return result; } public Json callWsImpl(String type, String[] arguments) { ThreadLocalStopwatch.start("START CallWsImpl: " + type); try { OWLNamedIndividual typeInd = (OWLNamedIndividual) Refs.configSet.resolve().get(type); OWLDataFactory factory = OWL.dataFactory(); List<SWRLDArgument> args = new ArrayList<SWRLDArgument>(); args.add(factory.getSWRLLiteralArgument(factory.getOWLLiteral(typeInd.getIRI().getFragment()))); // args.add(factory.getSWRLLiteralArgument(factory.getOWLLiteral(type))); for (String arg : arguments) { SWRLDArgument argSwrld = factory.getSWRLLiteralArgument(factory.getOWLLiteral(arg)); args.add(argSwrld); ThreadLocalStopwatch.now("NOW CallWsImpl arg: " + arg + " swrld: " + argSwrld); } args.add(factory.getSWRLVariable(OWL.fullIri("document"))); SWRLBuiltInAtom atom = factory.getSWRLBuiltInAtom(OWL.fullIri("webServiceCall"), args); WebServiceCallTask accountQuery = new WebServiceCallTask(); Map<SWRLVariable, OWLObject> eval = accountQuery.eval(atom, OWL.ontology(), new RefResolver<SWRLVariable, OWLObject>() { public OWLObject resolve(SWRLVariable v) { return null; } }); OWLObject obj = eval.entrySet().iterator().next().getValue(); OWLLiteral l = (OWLLiteral) obj; StreamSource xml = new StreamSource( new StringReader(l.getLiteral())); StreamSource xsl = new StreamSource(new File(StartUp.getConfig().at( "workingDir").asString() + "/src/resources/xml-2-json.xsl")); TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(xsl); StringWriter writer = new StringWriter(); StreamResult literal = new StreamResult(writer); try { transformer.transform(xml, literal); } catch (Exception e) { throw e; } Json result = Json.read(literal.getWriter().toString()); ThreadLocalStopwatch.stop("END CallWsImpl: " + type + " Success."); return ok().set("result", result); } catch (Throwable e) { e.printStackTrace(System.err); ThreadLocalStopwatch.error("ERROR CallWsImpl: " + type + " " + e); return ko(e.getMessage()); } } public static Json replaceAnswerValuesWithLabels(Json data) { if (!data.has("hasServiceAnswer")) return data; if (data.at("hasServiceAnswer").isArray()) { for (Json ans : data.at("hasServiceAnswer").asJsonList()) { replaceEachAnswerValuesWithLabels(ans); } } else replaceEachAnswerValuesWithLabels(data.at("hasServiceAnswer")); return data; } public static void replaceEachAnswerValuesWithLabels(Json ans) { if (ans.has("hasAnswerValue")) { if (ans.has("hasServiceField") && ans.at("hasServiceField").has("hasChoiceValueList")) { if (!ans.at("hasAnswerValue").isArray()) { if (!GenUtils.containsWhiteSpace(ans.at("hasAnswerValue") .asString())) { OWLNamedIndividual ansIndividual = OWL.individual(ans .at("hasAnswerValue").asString()); String label = OWL.getEntityLabel(ansIndividual); if (label != null) ans.set("hasAnswerValue", label); } } else if (ans.at("hasAnswerValue").isArray()) { Json answerValueArray = array(); for (Json eachAnswerValue : ans.at("hasAnswerValue") .asJsonList()) { if (!GenUtils.containsWhiteSpace(eachAnswerValue .asString())) { OWLNamedIndividual ansIndividual = OWL .individual(eachAnswerValue.asString()); String label = OWL.getEntityLabel(ansIndividual); if (label != null) answerValueArray.add(label); } } ans.set("hasAnswerValue", answerValueArray); } } } } @GET @Path("/validate") @Produces("application/json") public Json validateServiceOnXY(@QueryParam("type") String typeCode, @QueryParam("x") String x, @QueryParam("y") String y) { // if (1==1) // return Json.object().set("isAvailable", true); Json result = Json.object().set("isAvailable", false); try { Json propertyInfo = Json.object(); propertyInfo.set( "coordinates", Json.object().set("x", new BigDecimal(x)) .set("y", new BigDecimal(y))); result.set("isAvailable", Refs.gisClient.resolve().isAvailable(propertyInfo, OWL.fullIri("legacy:" + typeCode))); } catch (Exception e) { logger.log(Level.WARNING, "Could not validate service on x,y", e); } return result; } @POST @Path("/duplicateCheck") @Produces("application/json") public Json duplicateCheckService(@FormParam("data") String formData) { if (DBG) ThreadLocalStopwatch.startTop("START DuplicateCheck"); List<Map<String, Object>> duplicates; Json result = ok(); Json json = Json.read(formData); String srType = json.at("type").asString(); Json address = json.at("address"); String createdDate = json.has("createdDate") ? json.at("createdDate").asString() : null; String boid = json.has("boid") ? json.at("boid").asString() : null; String hasCaseNumber = json.has("hasCaseNumber") ? json.at("hasCaseNumber").asString() : null; try { String addrType = address.at("addressType").isNull() ? "" : address.at("addressType").asString(); String fullAddr = address.at("fullAddress").isNull() ? "" : address.at("fullAddress").asString(); String city = address.at("Street_Address_City").isNull() ? "" : address.at("Street_Address_City").at("iri").isNull() ? "" : address.at("Street_Address_City").at("iri").asString(); String state = address.at("Street_Address_State").at("iri") .asString(); Long zip = address.at("Zip_Code").isNull() ? 0 : address.at("Zip_Code").asLong(); String unit = address.has("Street_Unit_Number") ? address.at( "Street_Unit_Number").asString() : null; String locationName = address.has("hasLocationName") ? address.at( "hasLocationName").asString() : null; if (addrType.equals("PointAddress") || addrType.equals("StreetAddress") || addrType.equals("Address")) { Long streetNumber = address.at("Street_Number").asLong(); String streetName = address.at("Street_Name").asString(); String streetPrfx = address.at("Street_Direction").at("iri") .asString(); String streetSufx = address.at("hasStreetType").at("iri") .asString(); duplicates = duplicateCheck(owlClass(srType), streetNumber, streetName, streetPrfx, streetSufx, unit, locationName, city, state, zip, fullAddr, createdDate, false); } else { duplicates = duplicateCheck(owlClass(srType), (long) 0, null, null, null, unit, locationName, city, state, zip, fullAddr, createdDate, true); } if (duplicates == null) { result.set("count", 0).set("message", "No duplicate check rule configured for this type."); result.set("details", Json.array()); } else { Json details = Json.array(); Set<String> boids = new HashSet<String>(); for (Map<String, Object> duplicate : duplicates) { Json dup = Json.make(duplicate); boids.add(dup.at("boid").asString()); details.add(dup); } if(!boids.isEmpty()) for (Map<String, Object> rc : getAllRelatedCases(boids, boid, hasCaseNumber)) { details.add(rc); } result.set("count", details.asJsonList().size()); result.set("details", details).set("message", "Success"); } return result; } catch (Exception e) { System.out.println("formData passed into duplicateCheckService : "+formData); e.printStackTrace(); return ko(e); } finally { if (DBG) ThreadLocalStopwatch.getWatch().time("END DuplicateCheck"); ThreadLocalStopwatch.dispose(); } } /** * Checks for SR duplicates in the RelationStore based on the * duplicateCheckRule configuration in the ontology for the given srType. * The algorithm is based on what is described in the CSR Configuration * manual under: Setting Duplicate Check Method {@link http * ://olsserver:8080/ * wiki/attach/Docs/Configuration_Mgr_Guide_%28Combined%29_PDF_format.pdf} * * * @param srType * @param streetNumber * @param streetName * @param streetPrefix * @param streetSuffix * @param unit * @param locationName * @param city * @param state * @param zip * @return * @author Syed */ private List<Map<String, Object>> duplicateCheck(OWLClass srType, Long streetNumber, String streetName, String streetPrefix, String streetSuffix, String unit, String locationName, String city, String state, Long zip, String fullAddress, String createdDate, boolean Isintersection) throws Exception { long lo = ((long) streetNumber / 100) * 100; long hi = lo + 99; long thresholdDays = -1; // Search only SRs created on (today's date - // thresholdDays) when determining // duplicates. long addressBuffer = 0; // Address buffer is the range of streetNumbers // to check relative to the supplied // streetNumber, default is 0-99 (whole block) boolean useAddressBuffer = false; OWLClass statusLimit; OWLNamedIndividual duplicateCheckRule = objectProperty( individual(srType.getIRI().toString()), Model.legacy("hasDuplicateCheckRule").toString()); Set<OWLClass> S = null; if (duplicateCheckRule != null) S = reasoner().getTypes(duplicateCheckRule, true).getFlattened(); if (S == null || S.isEmpty()) return null; OWLClass type = S.iterator().next(); if (type.getIRI().equals(Model.legacy("StreetAddressOnlyCheckRule"))) { if (Isintersection == false) { useAddressBuffer = true; // use and address buffer only for // StreetAddressOnlyRule. OWLLiteral addressBufferLiteral = dataProperty( duplicateCheckRule, Model.legacy("hasAddressBuffer").toString()); if (addressBufferLiteral != null) { addressBuffer = addressBufferLiteral.parseInteger(); if (addressBuffer > 0) { if (addressBuffer > streetNumber) lo = 0; else lo = streetNumber - addressBuffer; hi = streetNumber + addressBuffer; } } } } else if (type.getIRI().equals(Model.legacy("FullAddressCheckRule"))) { /** * The default settings check the all address fields so nothing * extra to do here, except recognizing the rule itself in the else * condition. */ } else { throw new IllegalStateException("Unknown duplicate check rule:" + type.getIRI().toString()); } OWLLiteral thresholdDaysLiteral = dataProperty(duplicateCheckRule, Model.legacy("hasThresholdDays").toString()); if (thresholdDaysLiteral != null) { thresholdDays = thresholdDaysLiteral.parseInteger(); } OWLNamedIndividual statusLimitIndividual = objectProperty( duplicateCheckRule, Model.legacy("hasStatusLimit").toString()); if (statusLimitIndividual != null) { statusLimit = owlClass(statusLimitIndividual.getIRI()); } else { statusLimit = null; } Statement statement = new Statement(); List<Object> parameters = new ArrayList<Object>(); List<OWLNamedIndividual> parameterTypes = new ArrayList<OWLNamedIndividual>(); Sql select = SELECT(); select.FROM("CIRM_SR_REQUESTS_VIEW") .COLUMN("SR_REQUEST_ID").AS("\"boid\"") .COLUMN("FULL_ADDRESS").AS("\"fullAddress\"") .COLUMN("UNIT").AS("\"Street_Unit_Number\"") .COLUMN("SR_STATUS").AS("\"hasStatus\"") .COLUMN("CREATED_DATE").AS("\"hasDateCreated\"") .COLUMN("CASE_NUMBER").AS("\"hasCaseNumber\""); select.WHERE("SR_TYPE").EQUALS("?").AND().WHERE("CITY").EQUALS("?") .AND().WHERE("ZIP").EQUALS("?"); parameters.add(srType); parameterTypes.add(individual(Concepts.INTEGER)); OWLNamedIndividual cityIndividual = individual(city); parameters.add(cityIndividual); parameterTypes.add(individual(Concepts.INTEGER)); parameters.add(zip); parameterTypes.add(individual(Concepts.INTEGER)); // conditional if (thresholdDays > -1) { // select.AND().WHERE("CREATED_DATE").GREATER_THAN("SYSDATE - ?"); // parameters.add(thresholdDays); // parameterTypes.add(individual(Concepts.INTEGER)); select.AND().WHERE("CREATED_DATE"); if(createdDate == null) { select.GREATER_THAN("SYSDATE - ?"); parameters.add(thresholdDays); parameterTypes.add(individual(Concepts.INTEGER)); } else { select.GREATER_THAN("? - ?"); parameters.add(createdDate); parameterTypes.add(individual(Concepts.TIMESTAMP)); parameters.add(thresholdDays); parameterTypes.add(individual(Concepts.INTEGER)); } } if (useAddressBuffer) { select.AND().WHERE("STREET_NUMBER").BETWEEN("?", "?"); parameters.add(lo); parameterTypes.add(individual(Concepts.INTEGER)); parameters.add(hi); parameterTypes.add(individual(Concepts.INTEGER)); if (streetPrefix != null && !"".equals(streetPrefix)) { select.AND().WHERE("STREET_NAME_PREFIX").EQUALS("?"); OWLNamedIndividual streetPrefixIndividual = individual(streetPrefix); parameters.add(streetPrefixIndividual.getIRI().getFragment()); parameterTypes.add(individual(Concepts.VARCHAR)); } if (streetSuffix != null && !"".equals(streetSuffix)) { select.AND().WHERE("STREET_NAME_SUFFIX").EQUALS("?"); OWLNamedIndividual streetSuffixIndividual = individual(streetSuffix); parameters.add(streetSuffixIndividual.getIRI().getFragment()); parameterTypes.add(individual(Concepts.VARCHAR)); } if (streetName != null && !"".equals(streetName)) { select.AND().WHERE("STREET_NAME").EQUALS("?"); parameters.add(streetName); parameterTypes.add(individual(Concepts.VARCHAR)); } } else { select.AND().WHERE("FULL_ADDRESS").EQUALS("?"); parameters.add(fullAddress); parameterTypes.add(individual(Concepts.VARCHAR)); } if (unit != null && !unit.equals("")) { select.AND().WHERE("UNIT").EQUALS("?"); parameters.add(unit); parameterTypes.add(individual(Concepts.VARCHAR)); } else { select.AND().WHERE("UNIT IS NULL"); } if (locationName != null && !locationName.equals("")) { select.AND().WHERE("LOCATION_NAME"); parameters.add(locationName); parameterTypes.add(individual(Concepts.VARCHAR)); } else { select.AND().WHERE("LOCATION_NAME IS NULL"); } if (statusLimit != null) { Set<OWLNamedIndividual> statuses = reasoner().getInstances( statusLimit, false).getFlattened(); if (!statuses.isEmpty()) { List<String> fragments = new ArrayList<String>(statuses.size()); for (OWLNamedIndividual status : statuses) { fragments.add("'" + status.getIRI().getFragment() + "'"); } select.AND().WHERE("SR_STATUS") .IN(fragments.toArray(new String[fragments.size()])); } } statement.setSql(select); statement.setParameters(parameters); statement.setTypes(parameterTypes); RelationalStore store = getPersister().getStore(); if (DBGSQL) System.out.println("DUPLICATE \r\n" + select.SQL()); return store.query(statement, Refs.tempOntoManager.resolve().getOWLDataFactory()); } @POST @Path("/relatedCases") @Produces("application/json") public Json relatedCasesCheckService(@FormParam("data") String formData) { if(DBG) ThreadLocalStopwatch.startTop("START relatedCases"); Json json = Json.read(formData); String boid = json.has("boid") ? json.at("boid").asString() : null; String hasCaseNumber = json.has("hasCaseNumber") ? json.at("hasCaseNumber").asString() : null; Json result = ok(); Set<Map<String, Object>> relatedCases = null; try { Set<String> boids = new HashSet<String>(); for(Json j : json.at("boid").asJsonList()) boids.add(j.asString()); if(!boids.isEmpty()) relatedCases = getAllRelatedCases(boids, boid, hasCaseNumber); if (relatedCases == null) { result.set("count", 0) .set("message", "no relatedCases") .set("details", Json.array()); } else { result.set("count", relatedCases.size()); Json details = Json.array(); for (Map<String, Object> rc : relatedCases) { details.add(Json.make(rc)); } result.set("details", details).set("message", "Success"); } return result; } catch (Exception e) { System.out.println( "formData passed into relatedCasesCheckService : "+formData); e.printStackTrace(); return ko(e); } finally { if (DBG) ThreadLocalStopwatch.getWatch().time("END relatedCases"); ThreadLocalStopwatch.dispose(); } } private Set<Map<String, Object>> getAllRelatedCases(Set<String> boids, String boid, String hasCaseNumber) throws Exception { Set<Map<String, Object>> results = new HashSet<Map<String, Object>>(); results.addAll(getParentCases(boids)); if(boid != null && hasCaseNumber != null) results.addAll(getChildCases(boid, hasCaseNumber)); return results; } private List<Map<String, Object>> getParentCases(Set<String> boids) throws Exception { Statement statement = new Statement(); List<Object> parameters = new ArrayList<Object>(); List<OWLNamedIndividual> parameterTypes = new ArrayList<OWLNamedIndividual>(); Sql select = SELECT(); select .FROM("CIRM_SR_REQUESTS_VIEW srv, " + "CIRM_DATA_PROPERTY_VIEW dpv, CIRM_IRI iri") .COLUMN("srv.SR_REQUEST_ID").AS("\"boid\"") .COLUMN("iri.IRI").AS("\"type\"") .COLUMN("srv.FULL_ADDRESS").AS("\"fullAddress\"") .COLUMN("srv.UNIT").AS("\"Street_Unit_Number\"") .COLUMN("srv.SR_STATUS").AS("\"hasStatus\"") .COLUMN("srv.CREATED_DATE").AS("\"hasDateCreated\"") .COLUMN("srv.CASE_NUMBER").AS("\"hasCaseNumber\"") .WHERE("dpv.PREDICATE_ID").EQUALS("?") .AND().WHERE("dpv.TO_DATE is null") .AND().WHERE("dpv.SUBJECT_ID").IN( boids.toArray(new String[boids.size()])) .AND().WHERE("srv.SR_TYPE").EQUALS("iri.ID") .AND().WHERE("TO_CHAR(SRV.SR_REQUEST_ID)").EQUALS("dpv.VALUE_VARCHAR") .OR_ARRAY().WHERE("SRV.CASE_NUMBER").EQUALS("dpv.VALUE_VARCHAR"); parameters.add(dataProperty("legacy:hasParentCaseNumber")); parameterTypes.add(individual(Concepts.INTEGER)); statement.setSql(select); statement.setParameters(parameters); statement.setTypes(parameterTypes); RelationalStore store = getPersister().getStore(); if (DBG) System.out.println("ParentCases"); if (DBGSQL) System.out.println("" + select.SQL()); return store.query(statement, Refs.tempOntoManager.resolve().getOWLDataFactory()); } private List<Map<String, Object>> getChildCases(String boid, String hasCaseNumber) throws Exception { Statement statement = new Statement(); List<Object> parameters = new ArrayList<Object>(); List<OWLNamedIndividual> parameterTypes = new ArrayList<OWLNamedIndividual>(); Sql select = SELECT(); select .FROM("CIRM_SR_REQUESTS_VIEW srv, CIRM_DATA_PROPERTY_VIEW dpv, " + "CIRM_IRI iri") .COLUMN("srv.SR_REQUEST_ID").AS("\"boid\"") .COLUMN("iri.IRI").AS("\"type\"") .COLUMN("srv.FULL_ADDRESS").AS("\"fullAddress\"") .COLUMN("srv.UNIT").AS("\"Street_Unit_Number\"") .COLUMN("srv.SR_STATUS").AS("\"hasStatus\"") .COLUMN("srv.CREATED_DATE").AS("\"hasDateCreated\"") .COLUMN("srv.CASE_NUMBER").AS("\"hasCaseNumber\"") .WHERE("dpv.PREDICATE_ID").EQUALS("?") .AND().WHERE("dpv.TO_DATE is null") .AND().WHERE("dpv.SUBJECT_ID").EQUALS("srv.SR_REQUEST_ID") .AND().WHERE("srv.SR_TYPE").EQUALS("iri.ID") .AND().WHERE("dpv.VALUE_VARCHAR").EQUALS("?") .OR_ARRAY().WHERE("dpv.VALUE_VARCHAR").EQUALS("?"); parameters.add(dataProperty("legacy:hasParentCaseNumber")); parameterTypes.add(individual(Concepts.INTEGER)); parameters.add(boid); parameterTypes.add(individual(Concepts.VARCHAR)); parameters.add(hasCaseNumber); parameterTypes.add(individual(Concepts.VARCHAR)); statement.setSql(select); statement.setParameters(parameters); statement.setTypes(parameterTypes); RelationalStore store = getPersister().getStore(); if (DBG) System.out.println("ChildCases"); if (DBGSQL) System.out.println("" + select.SQL()); return store.query(statement, Refs.tempOntoManager.resolve().getOWLDataFactory()); } /** * Creates a new Service Request. * * 04/07/2015 -Sabbas - Annotated as a REST endpoint to provide access to a clean SR save with no SideEffects. * * @param sr : The Service Request data as json * @return : returns the SR * */ @POST @Path("sr") @Produces("application/json") @Consumes("application/json") public Json saveNewServiceRequestEndpoint(Json sr) { return saveNewServiceRequest(sr.toString()); } /** * Get the current Approval State of the SR. * * * @param caseNumber : The Service Request data stringified json * @return : returns the Business Ontology which is persisted to db in Json format * */ @GET @Path("sr/{caseNumber}/approvalState") @Produces("application/json") public Json getApprovalState(@PathParam("caseNumber") String caseNumber) { try { Json sr = OWL.prefix(lookupByCaseNumber(caseNumber)); ApprovalProcess approvalProcess = new ApprovalProcess(); approvalProcess.setSr(sr); return Json.object().set("caseNumber", caseNumber) .set("approvalState", approvalProcess.getApprovalState().toString()); } catch (Exception e) { return ko(e); } } /** * Approves an existing service request. * * @param formData : The Service Request data stringified json * @return : returns the Business Ontology which is persisted to db in Json format * */ @POST @Path("sr/approve") @Produces("application/json") @Consumes("application/json") public Json approveCase(final Json legacyform) { ThreadLocalStopwatch.startTop("START approveCase"); //We check if another user approved the case already after this user loaded the case to ensure //the case is only approved once. This must be done in the same transaction as the approval. //Status history of the case would need to be checked, because certain users can set a case from Locked to pending, //but we allow this for now until we are certain that this would never be needed. //For non-interface cases it may not be a problem and certain interface situations are thinkable, //where a repeated approval would make sense. try { return Refs.defaultRelationalStore.resolve().txn(new CirmTransaction<Json>() { public Json call() { Json result = null; Json bo = legacyform.has("bo")? legacyform.at("bo") : legacyform; long boid = bo.at("boid").asLong(); //Check if another user has approved case in the meantime. Json currentSR = lookupServiceCase(boid); String currentStatus = currentSR.at("bo").at("properties").at("hasStatus").at("iri").asString(); if (!currentStatus.contains("O-PENDNG")) { return ko("The status of the SR you tried to approve was modified by another user and is not Pending anymore.\n Please reload SR."); } else { //Still pending, approve case ThreadLocalStopwatch.stop("NOW SR is still in pending, approving with side effects"); ApprovalProcess approvalProcess = new ApprovalProcess(); approvalProcess.setSr(legacyform); approvalProcess.getSideEffects().add(new AttachSendEmailListener()); approvalProcess.getSideEffects().add(new CreateDefaultActivities()); approvalProcess.getSideEffects().add(new PopulateGisData()); approvalProcess.getSideEffects().add(new SaveOntology()); approvalProcess.getSideEffects().add(new CreateNewSREmail()); approvalProcess.getSideEffects().add(new AddTxnListenerForNewSR()); try { approvalProcess.approve(); } catch (Exception e) { throw new RuntimeException("Exception during approve SR", e); } ThreadLocalStopwatch.stop("END approveCase"); result = ok().set("bo", approvalProcess.getBOntology().toJSON()); return result; } }}); }catch(Exception e) { ThreadLocalStopwatch.fail("FAIL approveCase"); return ko(e); } } }